﻿// HSS.IO.Zip.ZipDirEntry.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       ZipDirEntry.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.IO.Zip
{
	#region Using Directives
	using System;
	#endregion

	#region ZipDirEntry
	/// <summary>
	/// This class models an entry in the directory contained within the zip file.
	/// The class is generally not used from within application code, though it is
	/// used by the ZipFile class.
	/// </summary>
	internal class ZipDirEntry
	{
		#region Fields
		private Int16 _InternalFileAttrs;
		private Int32 _ExternalFileAttrs;
		private byte[] _Extra;
		#endregion

		#region Constructor
		/// <summary>
		/// Constructor
		/// </summary>
		private ZipDirEntry() { }
		#endregion

		#region Properties
		/// <summary>
		/// The filename of the file represented by the given entry.
		/// </summary>
		public string FileName
		{
			get { return _FileName; }
		} string _FileName;
		/// <summary>
		/// Any comment associated to the given entry. Comments are generally optional.
		/// </summary>
		public string Comment
		{
			get { return _Comment; }
		} string _Comment;
		/// <summary>
		/// True if the referenced entry is a directory.  
		/// </summary>
		public bool IsDirectory
		{
			get { return ((_InternalFileAttrs == 0) && ((_ExternalFileAttrs & 0x0010) == 0x0010)); }
		}
		#endregion

		#region Methods
		/// <summary>
		/// Reads one entry from the zip directory structure in the zip file. 
		/// </summary>
		/// <param name="s">the stream from which to read.</param>
		/// <param name="expectedEncoding">
		/// The text encoding to use if the entry is not marked UTF-8.
		/// </param>
		/// <returns>the entry read from the archive.</returns>
		public static ZipDirEntry Read(System.IO.Stream s, System.Text.Encoding expectedEncoding)
		{
			int signature = SharedUtilities.ReadSignature(s);
			// return null if this is not a local file header signature
			if (ZipDirEntry.IsNotValidSig(signature))
			{
				s.Seek(-4, System.IO.SeekOrigin.Current);

				// Getting "not a ZipDirEntry signature" here is not always wrong or an error. 
				// This can happen when walking through a zipfile.  After the last ZipDirEntry, 
				// we expect to read an EndOfCentralDirectorySignature.  When we get this is how we 
				// know we've reached the end of the central directory. 
				if (signature != ZipConstants.EndOfCentralDirectorySignature)
				{
					throw new BadReadException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, "ZipDirEntry::Read(): Bad signature ({0:X8}) at position 0x{1:X8}", signature, s.Position));
				}
				return null;
			}

			byte[] block = new byte[42];
			int n = s.Read(block, 0, block.Length);
			if (n != block.Length) return null;

			int i = 0;
			ZipDirEntry zde = new ZipDirEntry();
			Int16 bitField = (short)(block[i++] + block[i++] * 256);			
			Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
			Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);
			Int16 commentLength = (short)(block[i++] + block[i++] * 256);
			i += 2;

			zde._InternalFileAttrs = (short)(block[i++] + block[i++] * 256);
			zde._ExternalFileAttrs = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;

			block = new byte[filenameLength];
			n = s.Read(block, 0, block.Length);
			if ((bitField & 0x0800) == 0x0800)
			{
				// UTF-8 is in use
				zde._FileName = SharedUtilities.Utf8StringFromBuffer(block, block.Length);
			}
			else
			{
				zde._FileName = SharedUtilities.StringFromBuffer(block, block.Length, expectedEncoding);
			}


			if (extraFieldLength > 0)
			{
				zde._Extra = new byte[extraFieldLength];
				n = s.Read(zde._Extra, 0, zde._Extra.Length);
			}
			if (commentLength > 0)
			{
				block = new byte[commentLength];
				n = s.Read(block, 0, block.Length);
				if ((bitField & 0x0800) == 0x0800)
				{
					// UTF-8 is in use
					zde._Comment = SharedUtilities.Utf8StringFromBuffer(block, block.Length);
				}
				else
				{
					zde._Comment = SharedUtilities.StringFromBuffer(block, block.Length, expectedEncoding);
				}
			}
			return zde;
		}
		/// <summary>
		/// Returns true if the passed-in value is a valid signature for a ZipDirEntry. 
		/// </summary>
		/// <param name="signature">the candidate 4-byte signature value.</param>
		/// <returns>true, if the signature is valid according to the PKWare spec.</returns>
		internal static bool IsNotValidSig(int signature)
		{
			return (signature != ZipConstants.ZipDirEntrySignature);
		}
		#endregion
	}
	#endregion
}